<?php
/**
 * Created by PhpStorm.
 * User: admin
 * Date: 2021.3.10
 * Time: 20:08
 */

namespace Illuminate\Container;

use Illuminate\Contracts\Container\BindingResultionException;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;
class Container
{

    protected $buildStack = [];
    protected $with = [];
    public function make($abstract,$parameter = [])
    {
        return $this->resolve($abstract,$parameter);
    }

    protected function resolve($abstract,$parameter = []) {
        $concrete = $abstract;

        //增加with属性
        $this->with[] = $parameter;
        return $this->build($concrete);
    }

    protected function build($concrete) {
        // 反射获取注入的类，如果类不存在的话，会报错,如果存在返回一个对象

        // 不存在的类，直接抛出异常
        try {
            $reflector = new ReflectionClass($concrete);
        } catch (ReflectionException $e) {
            $msg = "没有找到实例[{$concrete}]";
            throw new BindingResultionException($msg,0,$e);
        }

        // 像接口、抽象类、不能被实例话，得抛出异常
        if(!$reflector->isInstantiable()) {
            $this->notInstantiable($concrete);
        }


        // 将可以实例化的实例放入构建栈中
        $this->buildStack[] = $concrete;

        // 获取构造函数，如果构造函数不存在，返回null，如果存在返回一个对象
        $constructor = $reflector->getConstructor();

        // 这里判断构造函数里面是否有值，如果有值在进行下一步的解析，如果没有，直接返回实例化的类即可
        if(is_null($constructor)) {
            array_pop($this->buildStack);
            return new $concrete;
        }


        // 获取构造函数中的参数，以数组的方式
        $dependencies = $constructor->getParameters();

        // 抛出异常
        try {
            // 递归循环的，解析构造函数注入的类
            $instances = $this->resolveDependencies($dependencies);
        }catch (BindingResultionException $e) {
            array_pop($this->buildStack);
            throw $e;
        }



        array_pop($this->buildStack);
        // 创建一个类的新实例，给出的参数将传递到类的构造函数。这个意思就是将类依赖注入的类变为字符串
        return $reflector->newInstanceArgs($instances);
    }

    // 不能被实例化的类抛出异常
    public function notInstantiable($concrete) {

        if (!empty($this->buildStack)) {
            $concrete = implode(',',$this->buildStack);
        }

        $msg = "目标[{$concrete}]不能被实例话";
        throw new BindingResultionException($msg,0);
    }

    protected function resolveDependencies($dependencies) {
        $results = [];

        foreach ($dependencies as $dependency) {

            // 判读参数是否做了依赖覆盖
            if($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);
                continue;
            }


            // 判断构造函数传入的事一个类还是普通的参数
            if(!$dependency->getClass()) {
                $this->resolvePrimitive($dependency);
            } else {
                 //递归的调用类中的构造函数注入类
                $results[] = $this->make($dependency->getName());
            }

        }

        return $results;
    }

    protected function resolvePrimitive(ReflectionParameter  $dependency) {

        // 传入的不是一个类，那么来判断传入的参数有误默认值，如果没有默认值，那么就不能被解析
        if($dependency->isDefaultValueAvailable()) {
            return $dependency->getDefaultValue();
        }

        $this->unresolvablePrimitive($dependency);
    }



    protected function unresolvablePrimitive(ReflectionParameter $parameter) {
        $msg = "[{$parameter->getDeclaringClass()->getName()}]不能被解析";
        throw new BindingResultionException($msg);
    }

    protected function getParameterOverride($dependency) {
        return $this->getLastParameterOverride()[$dependency->name];
    }

    // 判断依赖是否做了参数覆盖
    protected function hasParameterOverride($dependency) {
        return array_key_exists(
            $dependency->name,$this->getLastParameterOverride()
        );
    }

    // 判断带with没有
    protected function getLastParameterOverride() {
        return count($this->with) ? end($this->with) : [];
    }
}

/**
 * 1、如果容器传入不存在的实例的，我们引入 异常类来返回自定义信息
 * 2、如果容器传入不能被实例化实例的类时，我们使用反射类的 inInstantable 方法来判断，否则抛出异常
 * 3、引入构建栈来传入不能被实例话的实例
 *
 *  解析依赖
 * 1、如果传入的依赖没有做约定，那么此时在使用获取反射类构造参数时
 * 2、如果传入的依赖是字符串或者是其他不能实例化的元素，我们用resolvePrimitive来判读
 */